
/**********************************************************************************\
** 文件名称: Servo.c
** 创建人员: YCG
** 创建日期: 2018-12-13
** 文档描述: 控制舵机与多通道PWM
应用：
*************1.放入定时器更新中断中，定时器的更新间隔建议小于0.01ms ，这样很占用资源
*************2.利用定时器的更新中断和通道比较中断，每个跟更新周期会进行多次比较
*************3.本文件还可以用来做PWM控制电机转动等
使用方法:
*************1.SerHead_t    定义一个控制头部
*************2.ServoHeadInit初始化控制头的相关参数
*************3.SerItem_t    定义控制对象
*************4.ServoPinInit 初始化引脚端口并添加进去控制头
*************6.ServoUpdate  按要求添加至相应的定时器中
*************5.ServoSet     设置控制对象的角度

** 版本代号: V0.1
** 版本说明: 初始版本
\********************************************************************************/
#include "Servo.h"
#include "uart.h"

SerHead_t SerHead;

/*******************************************************************************
** 函数名称: InitServoHead
** 功能描述: 舵机组初始化
** 参数说明: head: [输入]     舵机的控制头
**			 count: [输入]        整个周期的计数值
**			 maxAngle: [输入]     最大的角度
**			 startTime: [输入]    起始脉宽时间
**			 wideTime: [输入]     最大脉宽时间
**			 timeperiod: [输入]   周期时间
** 返回说明: None
** 创建人员: YCG
** 创建日期: 2018-12-13
********************************************************************************/
void ServoHeadInit(SerHead_t * const  head, float count, float maxAngle, float startTime, float wideTime, float timeperiod) {

    ListInit(&head->List);             //初始化列表

    head->Dat.AllCount = count;                                          //全部的计数值
    head->Dat.MaxCount = count * (startTime + wideTime) / timeperiod ;   //能够记的最大计数值
    head->Dat.OneceCount =  count * wideTime / timeperiod / maxAngle;	   //求每个角度所对应的脉宽
    head->Dat.StartCount =  count * startTime / timeperiod ;	           //起始脉宽计数
    head->List.DatPot = &head->Dat;                                      //将列表与列表的数据联系起来
}


/*******************************************************************************
** 函数名称: ServoPinInit
** 功能描述:
** 参数说明: head: [输入] 舵机控制头
**			 Ser: [输入]      舵机对象
**			 Port: [输入]     舵机引脚端口
**			 Pin: [输入/出]   舵机引脚
** 返回说明: None
** 创建人员: YCG
** 创建日期: 2018-12-14
********************************************************************************/
void ServoPinInit(SerHead_t* const head, SerItem_t* const Ser, GPIO_TypeDef * Port, uint32_t const Pin) {
    List_t *     const pxList     = &(head->List);  //获取当前的列表
    ListItem_t * const pxListItem = &(Ser->ListItem);  //获取当前的列表项

    ListItemInit(pxListItem);		      //初始化列表项

    Ser->Dat.Port = Port;             //初始化引脚端口
    Ser->Dat.Pin = Pin;               //初始化引脚序号
    pxListItem->DatPot = (SerItemDat_t*)&Ser->Dat;   //将列表项与列表项的数据联系起来

    pxListItem->Value = 0U;          //初始化列表项的值
    ListInsert(pxList, pxListItem);  //添加舵机列表项到舵机列表
}


/*******************************************************************************
** 函数名称: ServoSet
** 功能描述: 设置舵机的角度
** 参数说明: Ser: [输入]   需要设置的舵机
**			 angle: [输入]     需要设值的角度
** 返回说明: None
** 创建人员: YCG
** 创建日期: 2018-12-14
********************************************************************************/
void ServoSet(SerItem_t* const Ser, float const angle) {
    ListItem_t * const pxListItem = &(Ser->ListItem);  //获取当前的列表项
    List_t * const pxList = pxListItem->Head;          //获取当前的列表
    SerHeadDat_t * ListDat = (SerHeadDat_t *)pxList->DatPot;     //获取列表中存储信息的地址

    ListRemove(pxListItem);           //从列表中移除列表项
    pxListItem->Value = 	ListDat->StartCount + (ListDat->OneceCount * angle); //计算出脉宽值
    if(pxListItem->Value > ListDat->MaxCount) {   //如果大于计数值的话约束一下
        pxListItem->Value = ListDat->MaxCount;
    }
    ListInsert(pxList, pxListItem);   //添加舵机列表项到舵机列表
}

/*******************************************************************************
** 函数名称: UpdateReSetPin
** 功能描述: 更新舵机输出，在定时器中调用
** 参数说明: head: [输入]  控制的舵机对象
**			     time: [输入]  输入系统的时间
** 返回说明: 返回下一次期待进入的时间
** 创建人员: YCG
** 创建日期: 2018-12-14
********************************************************************************/
uint32_t ServoUpdate(SerHead_t* const head, uint32_t const time) {
    SerItemDat_t * dat;                           //列表项的数据指针，包含了引脚信息
    List_t *       const pxList = &(head->List);  //获取当前的列表
    SerHeadDat_t * ListDat = (SerHeadDat_t *)pxList->DatPot;     //获取列表中存储信息的地址
    uint32_t pxTime = time % (ListDat->AllCount);  //获取现在处于周期中的哪一个位置

    if(pxList->ListNum > 0) { //避免操作空的指针
        if(pxTime == 0) {  //表示一个周期的开始
            //遍历整个列表，将所有的引脚设为高电平
					     ListItem_t * PxListItem_t= pxList->EndItem.Next;      //获取第一个列表项
            for(LIST_TO_HEAD(pxList); LIST_IS_NOT_END(pxList); LIST_TO_NEXT(pxList)) {
                dat = (SerItemDat_t*)pxList->Index->DatPot;  //获取引脚信息
							if(pxList->Index->Value != 0U){
                LL_GPIO_SetOutputPin(dat->Port, dat->Pin);   //设置输出为高电平						
							}else{
                LL_GPIO_ResetOutputPin(dat->Port, dat->Pin); //设置输出为低电平    //为零的时候特殊处理
								PxListItem_t = pxList->Index;        //缓存此时的位置
							}
            }
						pxList->Index = PxListItem_t->Next;      //定位一下此时的位置
        } else {
            while((pxList->Index->Value <= pxTime) && LIST_IS_NOT_END(pxList)) { //判断当前的舵机是否满足条件
                dat = (SerItemDat_t*)pxList->Index->DatPot;  //获取引脚信息
                LL_GPIO_ResetOutputPin(dat->Port, dat->Pin); //设置输出为低电平
                LIST_TO_NEXT(pxList);                        //切换到下一个舵机列表项
            }
        }
            ListDat->Count = LIST_IS_NOT_END(pxList) ? pxList->Index->Value : pxList->EndItem.Next->Value; //下一次期待的比较值
			       //  Uprintf("num:%u\r\n",ListDat->Count );
        return ListDat->Count ;  //返回下一次期待的比较值
    }

    return 0 ;
}

/***************下面不是公共代码了--作为实例参考*****************/
/*******************************************************************************
** 函数名称: SerTimeStart
** 功能描述:
** 参数说明: None
** 返回说明: None
** 创建人员: YCG
** 创建日期: 2018-12-14
********************************************************************************/
void SerTimeStart(void) {
    LL_TIM_OC_DisablePreload(TIM14, LL_TIM_CHANNEL_CH1);  //不使能通道的预装载，这样每次装载比较值立即生效
    LL_TIM_EnableIT_CC1(TIM14);       //使能输出比较中断
    LL_TIM_EnableIT_UPDATE(TIM14);    //使能计数器更新中断
    LL_TIM_EnableCounter(TIM14);      //打开计数器开始计数
}

/*******************************************************************************
** 函数名称: Ser_IRQHandler
** 功能描述:
** 参数说明: None
** 返回说明: None
** 创建人员: YCG
** 创建日期: 2018-12-14
********************************************************************************/
void Ser_IRQHandler(void) {
    uint32_t nextTime = 0; //下一次比较的时间
    if(LL_TIM_IsActiveFlag_UPDATE(TIM14)) {      //更新中断
        LL_TIM_ClearFlag_UPDATE(TIM14);          //清除中断标志位
        nextTime = ServoUpdate(&SerHead, 0);     //输入零表示更新
    }
    if(LL_TIM_IsActiveFlag_CC1(TIM14)) {        //比较中断
        LL_TIM_ClearFlag_CC1(TIM14);            //清除中断标志位
        nextTime = ServoUpdate(&SerHead, LL_TIM_OC_GetCompareCH1(TIM14)); //用当前的比较值作为时间输入
    }
    if(nextTime != 0) {    //等于零的时候是已经到末尾了
        LL_TIM_OC_SetCompareCH1(TIM14, nextTime);  //设置下一次比较的值
    }
}



/********************************End of File************************************/
